
#include <QApplication>
#include <iostream>

extern "C"
{
#include "libavdevice/avdevice.h"    // 调用输入设备需要的头文件
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/pixfmt.h"
#include "libavutil/error.h"
#include "libswresample/swresample.h"
#include "libavfilter/avfilter.h"
#include "libavutil/time.h"
}

using namespace std;

int error(int ret)
{
    char buf[1024] = {0};
    av_strerror(ret, buf, sizeof(buf));
    cout << buf << endl;
    return -1;
}



int main(int argc, char *argv[])
{
    // QApplication app(argc, argv);
    char *inUrl = "./test.mp4";
    char *outUrl = "rtmp://172.30.170.50/live";

    bool stop_flag = false;

    const AVOutputFormat *ofmt;
    AVFormatContext *ifmt_ctx = nullptr;  //输入上下文
    AVFormatContext *ofmt_ctx = nullptr;  //输出上下文
    int ret;
    uint32_t i = 0;
    int videoIndex = -1;
    int frame_index = 0;
    int64_t start_time = 0;

    // av_register_all();
    avformat_network_init();
//    av_dict_set(&options, "video_size",  "640*480", 0);
//    av_dict_set(&options, "framerate",  "30", 0);
    //输入（Input）
    ret = avformat_open_input(&ifmt_ctx, inUrl, 0, 0);
    if (ret < 0) {
        cout <<  "ifmt_ctx avformat_open_input failed:" << ret;
        return -1;
    }
    ret = avformat_find_stream_info(ifmt_ctx, 0);
    if (ret < 0) {
        cout << "ifmt_ctx avformat_find_stream_info failed:"<< ret;
        return -1;
    }


    ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", outUrl);
    if (ret < 0)
    {
        cout << "ofmt_ctx avformat_alloc_output_context2 failed";
        return -1;
    }
    ofmt = ofmt_ctx->oformat;
    for (i = 0; i < ifmt_ctx->nb_streams; i++)
    {
        //这里开始要创建一个新的AVStream
        AVStream *stream = ifmt_ctx->streams[i];

        //判断是否是videoIndex。这里先记录下视频流。后面会对这个流进行操作
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoIndex = i;
        }

        //创建输出流
        const AVCodec *c = avcodec_find_decoder(stream->codecpar->codec_id);
        AVStream *os = avformat_new_stream(ofmt_ctx, c);

        //应该将编解码器的参数从input中复制过来
        // 这里要注意的是，因为 os->codec这样的取法，已经过时了。所以使用codecpar
        ret = avcodec_parameters_copy(os->codecpar, stream->codecpar);
        if (ret < 0)
        {
            cout << "ofmt_ctx os->codecpar avcodec_parameters_copy failed";
            return -1;
        }
        cout << "avcodec_parameters_copy success!" ;
        cout << "avcodec_parameters_copy success! in stream codec tag" << stream->codecpar->codec_tag;
        cout << "avcodec_parameters_copy success! out stream  codec tag" << os->codecpar->codec_tag ;
        //复制成功之后。还需要设置 codec_tag(编码器的信息？)
        os->codecpar->codec_tag = 0;
    }

    //检查一遍我们的输出
    av_dump_format(ofmt_ctx, 0, outUrl, 1);

    //开始使用io进行推流
    //通过AVIO_FLAG_WRITE这个标记位，打开输出的AVFormatContext->AVIOContext
    ret = avio_open(&ofmt_ctx->pb, outUrl, AVIO_FLAG_WRITE);
    if (ret < 0)
    {
        cout << "ofmt_ctx->pb avio_open failed" << ret;
        return -1;
    }
    cout << "avio_open success!";
    //先写头
    ret = avformat_write_header(ofmt_ctx, 0);
    if (ret < 0)
    {
        cout << "ofmt_ctx avformat_write_header failed" << ret;
        return -1;
    }
    //取得到每一帧的数据，写入
    AVPacket pkt;

    //为了让我们的代码发送流的速度，相当于整个视频播放的数据。需要记录程序开始的时间
    //后面再根据，每一帧的时间。做适当的延迟，防止我们的代码发送的太快了
    start_time = av_gettime();
    //记录视频帧的index，用来计算pts
    frame_index = 0;

    while (!stop_flag)
    {
        //输入输出视频流
        AVStream *in_stream, *out_stream;

        //从输入流中读取数据 frame到AVPacket当中
        ret = av_read_frame(ifmt_ctx, &pkt);
        if (ret < 0)
        {
            cout << "ifmt_ctx av_read_frame break";
            break;
        }

        //没有显示时间的时候，才会进入计算和校验
        //没有封装格式的裸流（例如H.264裸流）是不包含PTS、DTS这些参数的。在发送这种数据的时候，需要自己计算并写入AVPacket的PTS，DTS，duration等参数。如果没有pts，则进行计算
        if (pkt.pts == AV_NOPTS_VALUE)
        {
            //AVRational time_base：时基。通过该值可以把PTS，DTS转化为真正的时间。
            //先得到流中的time_base
            AVRational time_base = ifmt_ctx->streams[videoIndex]->time_base;
            //开始校对pts和 dts.通过time_base和dts转成真正的时间
            //得到的是每一帧的时间
            /*
            r_frame_rate 基流帧速率 。取得是时间戳内最小的帧的速率 。每一帧的时间就是等于 time_base/r_frame_rate
            av_q2d 转化为double类型
            */
            int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(ifmt_ctx->streams[videoIndex]->r_frame_rate);
            //配置参数  这些时间，都是通过 av_q2d(time_base) * AV_TIME_BASE 来转成实际的参数
            pkt.pts = (double)(frame_index * calc_duration) / (double)av_q2d(time_base) * AV_TIME_BASE;
            //一个GOP中，如果存在B帧的话，只有I帧的dts就不等于pts
            pkt.dts = pkt.pts;
            pkt.duration = (double)calc_duration / (double)av_q2d(time_base) * AV_TIME_BASE;
        }

        //开始处理延迟.只有等于视频的帧，才会处理
        if (pkt.stream_index == videoIndex)
        {
            //需要计算当前处理的时间和开始处理时间之间的间隔？？

            //0.先取时间基数
            AVRational time_base = ifmt_ctx->streams[videoIndex]->time_base;

            //AV_TIME_BASE_Q 用小数表示的时间基数。等于时间基数的倒数
            AVRational time_base_r = { 1, AV_TIME_BASE };

            //计算视频播放的时间. 公式等于 pkt.dts * time_base / time_base_r`
            //.其实就是 stream中的time_base和定义的time_base直接的比例
            int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_r);
            //计算实际视频的播放时间。 视频实际播放的时间=代码处理的时间？？
            int64_t now_time = av_gettime() - start_time;

            cout << time_base.num << " " << time_base.den << "  " << pkt.dts << "  " << pkt.pts << "   " << pts_time;
            //如果显示的pts time 比当前的时间迟，就需要手动让程序睡一会，再发送出去，保持当前的发送时间和pts相同
            if (pts_time > now_time)
            {
                //睡眠一段时间（目的是让当前视频记录的播放时间与实际时间同步）
                av_usleep((unsigned int)(pts_time - now_time));
            }
        }

        //重新计算一次pts和dts.主要是通过 in_s的time_base 和 out_s的time_base进行计算和校对
        //先取得stream
        in_stream = ifmt_ctx->streams[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];

        //重新开始指定时间戳
        //计算延时后，重新指定时间戳。 这次是根据 in_stream 和 output_stream之间的比例
        //计算dts时，不再直接用pts，因为如有有B帧，就会不同
        //pts，dts，duration都也相同
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt.duration = (int)av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        //再次标记字节流的位置，-1表示不知道字节流的位置
        pkt.pos = -1;

        //如果当前的帧是视频帧，则将我们定义的frame_index往后推
        if (pkt.stream_index == videoIndex)
        {
            cout << "Send" << frame_index << "video frames to output URL" ;
            frame_index++;
        }

        //发送！！！
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0)
        {
            cout << "发送数据包出错";
            break;
        }

        //使用完了，记得释放
        av_packet_unref(&pkt);
    }
    //写文件尾（Write file trailer）
    av_write_trailer(ofmt_ctx);
    if(ifmt_ctx){
        avformat_close_input(&ifmt_ctx);
    }
    if (ofmt_ctx && ofmt_ctx->pb && !(ofmt_ctx->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx->pb);
    if (ifmt_ctx) {
        avformat_free_context(ofmt_ctx);
    }

    if(ifmt_ctx){
        avformat_close_input(&ifmt_ctx);
    }
    if (ifmt_ctx && ofmt_ctx->pb && !(ofmt_ctx->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx->pb);
    if (ifmt_ctx) {
        avformat_free_context(ofmt_ctx);
    }

    return 0;
   //  return app.exec();
}